using System;
using System.Collections.Generic;
using System.Linq;

namespace CommandLineParsing
{
    public class ParameterSynonym
    {
        public string Name { get; set; }

        private IList<string> _synonymNames = new List<string>();
        public IList<string> SynonymNames
        {
            get { return _synonymNames; }
            set
            {
                _synonymNames = value;
                _synonymNamesLowercased = null;
            }
        }

        private IList<string> _synonymNamesLowercased;
        public IList<string> SynonymNamesLowercased
        {
            get
            {
                if (SynonymNames == null)
                {
                    return null;
                }

                if (_synonymNamesLowercased != null)
                {
                    return _synonymNamesLowercased;
                }

                _synonymNamesLowercased = SynonymNames.Select(p => p.ToLower()).ToList();

                return _synonymNamesLowercased;
            }
        }
    }

    public class Parameter
    {
        public string OriginalParameterText { get; set; }
        public string Prefix { get; set; }
        public string Name { get; set; }
        public string NameLowercased 
        {
            get { return Name.ToLower(); }
        }
        public string Value { get; set; }
        public string ValueSymbol { get; set; }
    }

    public class CommandLineParsingSettings
    {
        private IList<string> _parameterPrefixes = new List<string>()
                                                     {
                                                         "-",
                                                         "--",
                                                         "/"
                                                     };
        public IList<string> ParameterPrefixes
        {
            get { return _parameterPrefixes; }
            set
            {
                _parameterPrefixes = value;
                _parameterPrefixesSortedByLengthDescending = null;
            }
        }

        private IList<string> _parameterPrefixesSortedByLengthDescending;
        public IList<string> ParameterPrefixesSortedByLengthDescending
        {
            get
            {
                if (_parameterPrefixesSortedByLengthDescending != null)
                {
                    return _parameterPrefixesSortedByLengthDescending;
                }

                if (ParameterPrefixes == null)
                {
                    return new List<string>();
                }

                _parameterPrefixesSortedByLengthDescending = ParameterPrefixes.OrderByDescending(p => p.Length).ToList();

                return _parameterPrefixesSortedByLengthDescending;
            }
        }

        public IList<string> ParameterValueSymbols = new List<string>()
                                                         {
                                                             "=",
                                                             ":"
                                                         };

        public IList<ParameterSynonym> Synonyms = new List<ParameterSynonym>();

        public IList<Parameter> DefaultValues = new List<Parameter>();

    }

    internal class CommandLineParsingResults
    {
        public IList<string> Arguments = new List<string>();
        public IList<Parameter> Parameters = new List<Parameter>();

        public void Clear()
        {
            Arguments.Clear();
            Parameters.Clear();
        }
    }



    public class Parametero
    {
        private readonly CommandLineParsingResults _currentResults = new CommandLineParsingResults();

        public CommandLineParsingSettings Settings = new CommandLineParsingSettings();

        public IList<string> Arguments
        {
            get { return _currentResults.Arguments; }
        }
        public IList<Parameter> Parameters
        {
            get { return _currentResults.Parameters; }
        }

        public void Parse(string[] args)
        {
            _currentResults.Clear();

            if (args == null)
            {
                return;
            }

            ExtractArguments(args);
            ExtractParameters(args);

            return;
        }

        private Parameter DissasembleParameterString(string parameterString)
        {
            var result = new Parameter
                             {
                                 Prefix = Settings.ParameterPrefixesSortedByLengthDescending.FirstOrDefault(
                                     p => parameterString.StartsWith(p, StringComparison.InvariantCultureIgnoreCase)),
                                     OriginalParameterText = parameterString
                             };

            var parameterStringWithoutPrefix = result.Prefix != null
                                                   ? parameterString.Substring(result.Prefix.Length)
                                                   : parameterString;

            var valueSymbolFirstOccurencePositionsOrderedByThePosition =
                Settings.ParameterValueSymbols.Select(p => new { Symbol = p, Position = parameterStringWithoutPrefix.IndexOf(p)}).Where(o => o.Position > -1).OrderBy(a => a.Position);

            if (!valueSymbolFirstOccurencePositionsOrderedByThePosition.Any())
            {
                // no occurence of the value symbol so no value then
                result.Name = parameterStringWithoutPrefix;
                return result;
            }

            // we have value so lets extract it along with other maybe useful info about param

            var valueSymbolDef = valueSymbolFirstOccurencePositionsOrderedByThePosition.First();
            result.ValueSymbol = valueSymbolDef.Symbol;
            result.Value = parameterStringWithoutPrefix.Substring(valueSymbolDef.Position+1);
            result.Name = parameterStringWithoutPrefix.Remove(valueSymbolDef.Position);

            return result;
        }

        private void ExtractParameters(IEnumerable<string> args)
        {
            if (args == null)
            {
                return;
            }

            var allParameterStrings =
                args.Where(
                    a => Settings.ParameterPrefixesSortedByLengthDescending.Any(pr => a.StartsWith(pr, StringComparison.InvariantCultureIgnoreCase)));

            _currentResults.Parameters = allParameterStrings.Select(DissasembleParameterString).ToList();
        }

        private void ExtractArguments(IEnumerable<string> args)
        {
            if (args == null)
            {
                return;
            }

            _currentResults.Arguments =
                args.Where(
                    arg =>
                    arg != null &&
                    !Settings.ParameterPrefixes.Any(prefix => arg.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase)))
                    .ToList();
        }

        public string GetParameterValue(string name)
        {
            var paramWithoutSynonym = _currentResults.Parameters.Where(p => p.NameLowercased == name.ToLower()).FirstOrDefault();

            if (paramWithoutSynonym != null)
            {
                return paramWithoutSynonym.Value;

            }

            var synonymsForThisName = Settings.Synonyms.Where(p => p.SynonymNamesLowercased.Contains(name.ToLower()));

            if (!synonymsForThisName.Any())
            {
                throw new Exception(string.Format("Parameter {0} does not exist.", name));
            }

            // we have synonyms
            var lowercaseNamesOfAllParametersCurrentlySet = _currentResults.Parameters.Select(p => p.NameLowercased);

            foreach (var parameterSynonym in synonymsForThisName)
            {
                var definedParamNamesThatAreSynonymForThisSynonym =
                    parameterSynonym.SynonymNamesLowercased.Intersect(lowercaseNamesOfAllParametersCurrentlySet).
                        FirstOrDefault();
                if (definedParamNamesThatAreSynonymForThisSynonym != null)
                {
                    return GetParameterValue(definedParamNamesThatAreSynonymForThisSynonym);
                }
            }

            throw new Exception(string.Format("Parameter {0} does not exist.", name));          
        }

        public bool ParameterIsSet(string name)
        {
            var parameterSynonyms = Settings.Synonyms.Where(p => p.SynonymNames.Contains(name)).FirstOrDefault();

            if (parameterSynonyms == null)
            {
                return _currentResults.Parameters.Any(p => p.NameLowercased == name.ToLower());
            }

            var foundWithoutSynonyms = _currentResults.Parameters.Any(p => p.NameLowercased == name.ToLower());
            
            if (foundWithoutSynonyms)
            {
                return true;
            }

            // lets see if its there but via synonym
            var synonymsForThisParameterName = Settings.Synonyms.Where(s => s.SynonymNamesLowercased.Contains(name.ToLower()));

            var namesOfRealParameterNamesCurrentlySet = _currentResults.Parameters.Select(p => p.NameLowercased);

            foreach (var synonym in synonymsForThisParameterName)
            {
                if (namesOfRealParameterNamesCurrentlySet.Intersect(synonym.SynonymNamesLowercased).Any())
                {
                    return true;
                }
            }

            return false;
        }
    }
}